// AESHelper.spec.ts
import crypto from 'node:crypto'

import { beforeEach, describe, expect, it, vi } from 'vitest'

import { AESHelper } from '../src/main/utils/aesHelper'

vi.mock('@core/picgo', () => ({
  default: { getConfig: vi.fn(() => undefined) }
}))
vi.mock('~/utils/configPaths', () => ({
  configPaths: { settings: { aesPassword: 'settings.aesPassword' } }
}))

const HEX_RE = /^[0-9a-f]+$/i

describe('AESHelper', () => {
  beforeEach(() => {
    vi.clearAllMocks()
  })

  it('round-trips (encrypt -> decrypt) with explicit password', () => {
    const helper = new AESHelper('testPass123')
    const plaintext = JSON.stringify({ hello: 'world', n: 42 })
    const enc = helper.encrypt(plaintext)
    const dec = helper.decrypt(enc)
    expect(dec).toBe(plaintext)
  })

  it('produces iv:cipher hex format with 16-byte IV', () => {
    const helper = new AESHelper('formatPass')
    const enc = helper.encrypt('format-check')
    const parts = enc.split(':')
    expect(parts.length).toBe(2)
    const [ivHex, cipherHex] = parts
    expect(ivHex.length).toBe(32)
    expect(HEX_RE.test(ivHex)).toBe(true)
    expect(cipherHex.length).toBeGreaterThan(0)
    expect(HEX_RE.test(cipherHex)).toBe(true)
  })

  it('is non-deterministic (random IV) for the same plaintext', () => {
    const helper = new AESHelper('randomIvPass')
    const pt = 'same-plaintext'
    const a = helper.encrypt(pt)
    const b = helper.encrypt(pt)
    expect(a).not.toBe(b)
    expect(helper.decrypt(a)).toBe(pt)
    expect(helper.decrypt(b)).toBe(pt)
  })

  it('returns "{}" on malformed inputs (compatibility behavior)', () => {
    const helper = new AESHelper('badInputPass')
    expect(helper.decrypt('')).toBe('{}') // empty
    expect(helper.decrypt('nocolonhere')).toBe('{}') // no separator
    expect(helper.decrypt('00:abcd')).toBe('{}') // IV too short
  })

  it('returns "{}" if ciphertext is tampered', () => {
    const helper = new AESHelper('tamperPass')
    const enc = helper.encrypt('secure-data')
    const [ivHex, cipherHex] = enc.split(':')
    const last = cipherHex.at(-1)!
    const flipped = last.toLowerCase() === 'a' ? 'b' : 'a'
    const tampered = `${ivHex}:${cipherHex.slice(0, -1)}${flipped}`
    expect(helper.decrypt(tampered)).toBe('{}')
  })

  it('two instances with the same password can decrypt each other', () => {
    const h1 = new AESHelper('sharedPass')
    const h2 = new AESHelper('sharedPass')
    const enc1 = h1.encrypt('hello')
    const enc2 = h2.encrypt('world')
    expect(h2.decrypt(enc1)).toBe('hello')
    expect(h1.decrypt(enc2)).toBe('world')
  })

  it('works with default constructor (uses picgo fallback password)', async () => {
    const h1 = new AESHelper()
    const h2 = new AESHelper()
    const enc = h1.encrypt('fallback-ok')
    expect(h2.decrypt(enc)).toBe('fallback-ok')
  })

  it('caches derived keys for the same password (pbkdf2Sync called once)', () => {
    const spy = vi.spyOn(crypto, 'pbkdf2Sync')
    const uniquePwd = 'cache-me-please-' + Math.random().toString(36).slice(2)

    const a = new AESHelper(uniquePwd)
    const b = new AESHelper(uniquePwd)
    a.encrypt('x')
    b.encrypt('y')

    const callsForPwd = spy.mock.calls.filter(args => args[0] === uniquePwd)
    expect(callsForPwd.length).toBe(1)
  })
})
